<?php

define("LEARNER", "inc/svm/svm_multiclass_learn");
define("CLASSIFIER", "inc/svm/svm_multiclass_classify");


/**
 * Convert a user represented by a sequence of numbers into a string to be used
 * by the classifier.
 * @param array $user An array contianing a set of values (features)
 * @param int $label The label to assign to the vector (if any)
 * @param array $skip The indeces of features to skip (you don't want to compute)
 * @return string The string representig a row in the classifier file
 */
function user_to_example($user, $label = 0, $skip = array()) {
    $output = "";
    $count = 1;
    $fid = 1;

    if ($label != 0)
        $output .= $label . " ";
    //$user = normalize($user);
    foreach ($user as $el) {
        if (!in_array($count, $skip)) {
            if ($el != "" && $el != 0) {
                $output .= "$fid:$el ";
            }
            $fid++;
        }
        $count++;
    }
    //print "$output</br>\n";
    return $output;
}


function normalize_vector($vector) {
    $norm = 0;
    $nvector = array();
    foreach ($vector as $el) {
        $norm += pow($el, 2);
    }
    $norm = sqrt($norm);
    if ($norm != 0) {
        foreach($vector as $el) {
            array_push($nvector, $el/$norm);
        }
    }
    return $nvector;
}

/**
 * Convert csv input feature files into SVM features
 * @param array $labels A vector representing the labels for each of the features
 * in the feature file
 * @param string $features The features file (CSV) to be converted into the SVM
 * format
 * @param string $delimiter The delimiter used in the csv file
 * @param array $skip The indeces of features to skip (not to consider)
 * @return string The string in SVM format of the features plus the labels
 */
function csv_to_examples($labels, $features, $delimiter = ",", $skip = array()) {
    $output = "";
    $count = 0;

    if (($fp = fopen($features, "r")) !== false) {
        try {
            while (($data = fgetcsv($fp, 0, $delimiter)) !== false) {
                $output .= user_to_example($data, $labels[$count], $skip);
                $output .= "\n";
                $count++;
            }
        } catch (Exception $e) {
            print $e . "\n";
        }
        fclose($fp);
    }
    return $output;
}

/**
 * Convert the csv label file into a vector
 * @param string $csv The filename of the csv file to be converted
 * @return array An array of labels, each line of the file represents a different
 * label
 */
function csv_to_labels($csv) {
    $output = array();

    if (($fp = fopen($csv,"r")) !== false) {
        try {
            while (($data = fgetcsv($fp, 1000, ",")) !== false) {
                array_push($output, $data[0]);
            }
            fclose($fp);
        } catch (Exception $e) {
            print $e . "\n";
        }
    }
    return $output;
}

/**
 * Convert a vector of users (matrix) into the corresponding SVM example file
 * @param array $labels An array of labels each value is assigned to the corresponding
 * feature vector in $vector
 * @param  $vector The input feature vector, each row represents a differnt user
 * @return string The string representing the features in SVM format
 */
function vectors_to_examples($labels, $vector) {
    $count = 0;
    $output = "";
    foreach ($vector as $data) {
        $output .= user_to_example($data, $labels[$count]);
        $output .= "\n";
        $count++;
    }
    return $output;
}

/**
 * Write a string into a file (if possible, otherwise an error will be thrown)
 * @param string $string The string to be written into the file
 * @param string $file The filename of the file to be written
 */
function write_to_file($string, $file) {
    if (($fp = fopen($file, "w")) !== false) {
        fwrite($fp, $string);
        fclose($fp);
    }
}

/**
 * Call the learner, that is an SVM-Multiclass (http://svmlight.joachims.org/svm_multiclass.html)
 * which requires a set of parameters to work
 * @param string $examples The example file you use as training set
 * @param string $model The model file to be created
 * @param bool $verbose If true prints additional messages
 * @param float $c The C parameter of the classifier. It regulates the trade-off
 * between errors and overfitting
 * @param int $kernel The kernel type, default is 0 = linear
 * @return The return value of the calling
 */
function learn($examples, $model, $verbose = false, $c = 0.01, $kernel = 0) {
    $output = array();
    exec(LEARNER ." -c $c -t $kernel $examples $model", $output, $retval);
    if ($verbose)
        foreach ($output as $out)
            print($out . "\n");
    return $retval;
}

/**
 * Call the classifier, which assings the labels to each user, using a predefined
 * model (call learn before to learn the model)
 * @param string $model The model file created in the learning phase
 * @param array $users The input feature vector: each row represents a differnt user
 * @param array $labels [optional]<br />The array of labels, each row is the corresponding label for the user
 * @param float $errors [optional]<br />The 0/1 errors to be stored after the classification
 * @param bool $verbose [optional]<br />If true prints additional messages
 * @return An array of classes for each user in the input vector
 */
function classify($model, $users, $labels = array(), &$errors = 0.0, $verbose = false) {
    $output = array();
    $examples = "";
    $label = 0;
    $outlabels = array();
    $temp = time().".feat";

    foreach($users as $index => $user) {
        if (array_key_exists($index, $labels))
            $label = $labels[$index];
        $examples .= user_to_example($user, $label) . "\n";
    }
    write_to_file($example, $temp);
    //Call the classifier
    $outlabels = classifier($temp, $model, $errors, $verbose);
    unlink($temp);
    return $outlabels;
}

/**
 * Call the classifier to classify the input set of users using an already learnt
 * model
 * @param string $test The file representing the examples to be classified. File MUST
 * be in SVM format
 * @param string $model The model previosly created (call learn to create a model)
 * @param float $errors [optional]<br />The 0/1 errors to be stored after the classification
 * @param bool $verbose [optional]<br />If true prints additional messages
 * @return An array containing the labels for each user
 */
function classifier($test, $model, &$errors = 0.0, $verbose = false) {
    $output = array();
    
    $predictions = "results/predictions_".time().".pred";
    $retval = 0;
    $outlabels = array();
    $cmd = CLASSIFIER . " $test $model $predictions";
    exec($cmd, $output, $retval);
    if ($verbose) {
        print $cmd."\n";
        print "Return value: $retval\n";
        foreach ($output as $out)
            print($out . "\n");
    }
    $out = implode($output);
    try {
        list($errors) = explode(" ", substr($out, strpos($out, "Zero/one-error on test set:") + 28));
        $errors += 0.0;
    } catch (Exception $ex) {
        if ($verbose)
            print_r($ex);
    }

    //Load classes from predictions
    if (($fp = fopen($predictions, "r")) !== false) {
        // Initialize a new array for the weight vector, and reset it to 0s;
        while (($line = fgets($fp)) !== false) {
            // For every feature
            array_push($outlabels, strtok($line, " ") + 0);
        }
        fclose($fp);
    }
    //unlink($predictions);
    return $outlabels;
}

/**
 * Compute weights starting from the model. This functions works only if
 * the used kernel is linear.
 * @param string $model A model file from which you want to compute the feature weights
 * @return array containing the weights for each feature
 */
function compute_weights($model) {
    $weights = array();

    // Skip the first 7 lines, we want the line with highest feature index
    if (($fp = fopen($model, "r")) !== false) {
        for ($i = 0; $i < 14; $i++) {
            fgets($fp);
        }
        // Initialize a new array for the weight vector, and reset it to 0s;
        while (($line = fgets($fp)) !== false) {
            // For every feature
            $alpha = strtok($line, " ");
            $tok = strtok(" ");

            while (($tok = strtok(" ")) !== false) {
                list($index, $value) = explode(":", $tok);

                $value = $alpha * $value;
                $oldValue = 0.;
                // Add old value, if present
                if (array_key_exists($index, $weights))
                    $oldValue = $weights[$index];

                $value += $oldValue;
                $weights[$index] = $value;
            }
        }
        fclose($fp);
    }
    return $weights;
}

/**
 * Computes the nDCG (http://en.wikipedia.org/wiki/Discounted_cumulative_gain) for
 * a list of results and the ideal one
 * @param array $ideal The array of classes in the ideal list
 * @param array $newlist The array of classes as output of the classifier
 * @return float the ndcg of the classification
 */
function ndcg($ideal, $newlist) {
    $ndcg = 0.0;
    $idcg = 0.0;
    $count = 0;

    asort($newlist);
    foreach ($newlist as $index => $label) {
        if ($count == 0){
            $ndcg += $ideal[$index];
            $idcg += $ideal[$count];
        } else {
            $ndcg += $ideal[$index]/log($count, 2);
            $idcg += $ideal[$count]/log($count, 2);
        }
    }
    return $ndcg / $idcg;
}

/**
 * Compute a distance between the classification and the input labels. The distance
 * is computed as sum_i(|ideal(i)-new(i)|/N where N) / k is the number of classes and ideal(i)
 * and new(i) is the class of ith user in the ideal and new vecotor respectively.
 * Note that the indeces of the users in ideal and new MUST be the same.
 * @param array $ideal The array of classes in the ideal list
 * @param array $newlist The array of classes as output of the classifier
 * @param int $class_num The total number of classes
 * @param int $k [optional]<br/> The number of elements to be considered
 */
function normalizedDistance($ideal, $newlist, $class_num, $k = 0) {
    $distance = 0.0;
    if ($k === 0 || $k > count($ideal))
        $k = count ($ideal);
    for ($i = 0; $i < $k; $i++) {
        $distance += abs($ideal[$i] - $newlist[$i])/$class_num;
    }
    return $distance/$k;
}


/**
 * Returns a list of subfolders for a given directory
 * @param string $dir The directory path
 * @return array An array of subdirectory names
 */
function get_subfolders($dir) {
    $folders = array();
    if (false !== ($files = scandir($dir))) {
        foreach ($files as $file) {
            if ($file != "." && $file != ".." && is_dir($dir.DIRECTORY_SEPARATOR.$file)) {
                array_push($folders, $file);
            }
        }
    }
    return $folders;
}

?>

